function eq(a, b) {
  // === 结果为 true，区别出 +0 和 -0
  if (a === b) return a !== 0 || 1 / a === 1 / b
  if (null == a || null == b) return false
  // 利用 NaN 和自身不等的特性，判断两个 NaN
  if (a !== a)  return b !== b
  var type = typeof a
  if ('function' !== type && 'object' !== type && 'object' !== b) return false
  // 比较两个复杂类型
  return deepEq(a, b)
}

let toString = Object.prototype.toString
function isFunction (obj) {
  return toString.call(obj) === '[object Function]'
}

function deepEq(a, b) {
  let className = toString.call(a)
  // 两个复杂类型不同类
  if (className !== toString.call(b)) return false
  // 进行类型转换
  switch (className) {
    case '[object RegExp]':
    case '[object String]':
        return '' + a === '' + b
    case '[object Number]':
        return +a === 0 ? 1 / +a === 1 / b : +a === +b
    case '[object Date]':
    case '[object Boolean]':
        return +a === +b
  }
  // 比较数组和对象
  let areArrays = className === '[object Array]'
  if (!areArrays) {
    // 过滤掉两个函数的情况
    if ('object' !== typeof a || 'object' !== typeof b) return false
    let aCtor = a.constructor, bCtor = b.constructor
    // aCtor 和 bCtor 必须都存在并且都不是 Object 构造函数的情况下，aCtor 不等于 bCtor， 那这两个对象就真的不相等啦
    // var attrs = Object.create(null);
    // attrs.name = "Bob";
    // eq(attrs, {name: "Bob"}); // ???
    if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {
      return false;
    }
  }
  if (areArrays) {
    // 判断数组
    let length = a.length
    if (length !== b.length)  return false
    while (length--) {
      if (!eq(a[length], b[length]))  return false
    }
  } else {
    // 判断对象
    let keys = Object.keys(a), key
    let length = keys.length
    if (Object.key(b) !== length) return false
    while (length--) {
      key = keys[length]
      if (!(b.hasOwnProperty(key) && eq(a[key], b[key]))) return false
    }
  }
  return true
}

// 存在循环引用的问题没有解决，完整版链接：https://github.com/mqyqingfeng/Blog/issues/41
